package msc.ai.crypto.jade.security.keymanager;

import jade.util.leap.ArrayList;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.*;
import java.io.*;
import java.security.*;

/**
 * Created by IntelliJ IDEA.
 * User: prageeth
 * Date: 5/12/12
 * Time: 9:57 AM
 * To change this template use File | Settings | File Templates.
 */
public class SpecialSealedObject// extends SealedObject
{
    /**
     * The encoded algorithm parameters.
     */
//    protected byte[] encodedParams;

    /**
     * The serialized, encrypted object.
     */
    private byte[] encryptedContent;

    /**
     * The algorithm used to seal the object.
     */
    private String sealAlg;

    /**
     * The parameter type.
     */
    private String paramsAlg;

    /**
     * The cipher that decrypts when this object is unsealed.
     */
    private transient Cipher sealCipher;

    /**
     * Compatible with JDK1.4.
     */
    private static final long serialVersionUID = 4482838265551344752L;

    public SpecialSealedObject(Serializable serializable, Cipher cipher, Key keyPair) throws IOException, IllegalBlockSizeException
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(serializable);
        oos.flush();
        ArrayList list = new ArrayList();
        try
        {
            byte[] bytes = baos.toByteArray();
            try
            {
                encryptedContent = encrypt(new String(bytes), cipher, keyPair).getBytes();
            } catch (Exception e)
            {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
//            encryptedContent = cipher.doFinal();
        } catch (IllegalStateException ise)
        {
            throw new IOException("cipher not in proper state");
        }
//        catch (BadPaddingException bpe)
//        {
//            throw new IOException(
//                    "encrypting but got javax.crypto.BadPaddingException");
//        }
        sealAlg = cipher.getAlgorithm();
//        encodedParams = cipher.getParameters().getEncoded();
//        paramsAlg = cipher.getParameters().getAlgorithm();

    }

    public String encrypt(String plaintext, Cipher cipher, Key keypair) throws Exception
    {
        cipher.init(Cipher.ENCRYPT_MODE, keypair);
        byte[] bytes = plaintext.getBytes("UTF-8");

        byte[] encrypted = blockCipher(bytes, Cipher.ENCRYPT_MODE, cipher);

        char[] encryptedTranspherable = Hex.encodeHex(encrypted);
        return new String(encryptedTranspherable);
    }

    private byte[] blockCipher(byte[] bytes, int mode, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException
    {
        // string initialize 2 buffers.
        // scrambled will hold intermediate results
        byte[] scrambled = new byte[0];

        // toReturn will hold the total result
        byte[] toReturn = new byte[0];
        // if we encrypt we use 100 byte long blocks. Decryption requires 128 byte long blocks (because of RSA)
        int length = (mode == Cipher.ENCRYPT_MODE) ? 100 : 128;

        // another buffer. this one will hold the bytes that have to be modified in this step
        byte[] buffer = new byte[length];

        for (int i = 0; i < bytes.length; i++)
        {

            // if we filled our buffer array we have our block ready for de- or encryption
            if ((i > 0) && (i % length == 0))
            {
                //execute the operation
                scrambled = cipher.doFinal(buffer);
                // add the result to our total result.
                toReturn = append(toReturn, scrambled);
                // here we calculate the length of the next buffer required
                int newlength = length;

                // if newlength would be longer than remaining bytes in the bytes array we shorten it.
                if (i + length > bytes.length)
                {
                    newlength = bytes.length - i;
                }
                // clean the buffer array
                buffer = new byte[newlength];
            }
            // copy byte into our buffer.
            buffer[i % length] = bytes[i];
        }

        // this step is needed if we had a trailing buffer. should only happen when encrypting.
        // example: we encrypt 110 bytes. 100 bytes per run means we "forgot" the last 10 bytes. they are in the buffer array
        scrambled = cipher.doFinal(buffer);

        // final step before we can return the modified data.
        toReturn = append(toReturn, scrambled);

        return toReturn;
    }

    private byte[] append(byte[] prefix, byte[] suffix)
    {
        byte[] toReturn = new byte[prefix.length + suffix.length];
        for (int i = 0; i < prefix.length; i++)
        {
            toReturn[i] = prefix[i];
        }
        for (int i = 0; i < suffix.length; i++)
        {
            toReturn[i + prefix.length] = suffix[i];
        }
        return toReturn;
    }


    public final Object getSObject(Cipher cipher, Key key)
            throws IOException, ClassNotFoundException, InvalidKeyException,
            NoSuchAlgorithmException
    {
//        try
//        {
//            if (sealCipher == null)
//            {
        sealCipher = cipher;//Cipher.getInstance(sealAlg);
//            }
//        } catch (NoSuchPaddingException nspe)
//        {
//            throw new NoSuchAlgorithmException(nspe.getMessage());
//        }
//        AlgorithmParameters params = null;
//        if (encodedParams != null)
//        {
//            params = AlgorithmParameters.getInstance(paramsAlg);
//            params.init(encodedParams);
//        }
        try
        {
//            sealCipher.init(Cipher.DECRYPT_MODE, key, params);
            return unseal();
        }
//        catch (InvalidAlgorithmParameterException iape)
//        {
//            throw new IOException("bad parameters");
//        }
        catch (IllegalBlockSizeException ibse)
        {
            throw new IOException("illegal block size");
        } catch (BadPaddingException bpe)
        {
            throw new IOException("bad padding");
        }
    }

    public String decrypt(String encrypted, KeyPair keypair, Cipher cipher) throws Exception
    {
        cipher.init(Cipher.DECRYPT_MODE, keypair.getPrivate());
        byte[] bts = Hex.decodeHex(encrypted.toCharArray());

        byte[] decrypted = blockCipher(bts, Cipher.DECRYPT_MODE, cipher);

        return new String(decrypted, "UTF-8");
    }

    private Object unseal()
            throws IOException, ClassNotFoundException, IllegalBlockSizeException,
            BadPaddingException
    {
        ByteArrayInputStream bais = null;
        try
        {
            bais = new ByteArrayInputStream(sealCipher.doFinal(encryptedContent));
//            try
//            {
//                bais = new ByteArrayInputStream(decrypt(new String(encryptedContent), keyPair, sealCipher ).getBytes());
//            } catch (Exception e)
//            {
//                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
//            }
        } catch (IllegalStateException ise)
        {
            throw new IOException("cipher not initialized");
        }
        ObjectInputStream ois = new ObjectInputStream(bais);
        return ois.readObject();
    }


}
